using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace ReactiveCode.Linq
{
    using Observables;

    partial class ObservableEnumerable
    {
        class VariableCollection<T>: Collection<Variable<T>>, IObservableEnumerable<T>
        {
            EnumerableSubject<T> _listeners = new EnumerableSubject<T>();

            public IDisposable Subscribe(IEnumerableObserver<T> observer)
            {
                observer.OnReset(this.OfType<IObservable<T>>());
                return _listeners.Subscribe(observer);
            }

            protected override void InsertItem(int index, Variable<T> item)
            {
                base.InsertItem(index, item);
                _listeners.OnAdded(item, index);
            }
            protected override void SetItem(int index, Variable<T> item)
            {
                var oldItem = this[index];

                base.SetItem(index, item);

                _listeners.OnRemoved(oldItem, index);
                _listeners.OnAdded(item, index);
            }
            protected override void RemoveItem(int index)
            {
                var item = this[index];
                base.RemoveItem(index);
                _listeners.OnRemoved(item, index);
            }
            protected override void ClearItems()
            {
                base.ClearItems();
                _listeners.OnReset(Enumerable.Empty<IObservable<T>>());
            }
        }
        public static IObservableEnumerable<T> AsObservableEnumerable<T>(this ObservableCollection<T> collection)
        {
            Contract.Requires(collection != null);
            return Create<T>(o =>
            {
                // OPTIMIZE: reuse this collection
                var local = new VariableCollection<T>();
                NotifyCollectionChangedEventHandler onChanged = (s, e) =>
                {
                    switch (e.Action)
                    {
                        case NotifyCollectionChangedAction.Add:
                        case NotifyCollectionChangedAction.Move:
                        case NotifyCollectionChangedAction.Remove:
                        {
                            if (e.NewItems != null)
                            {
                                int index = e.NewStartingIndex;
                                foreach (T item in e.NewItems)
                                    local.Insert(index++, new Variable<T>(item));
                            }
                            if (e.OldItems != null)
                            {
                                foreach (T item in e.OldItems)
                                    local.RemoveAt(e.OldStartingIndex);
                            }
                            break;
                        }
                        case NotifyCollectionChangedAction.Replace:
                        {
                            int index = e.NewStartingIndex;
                            foreach (T newItem in e.NewItems)
                            {
                                Contract.Assert(Equals(local[index].Value, (T)e.OldItems[index]));
                                local[index].Value = newItem;
                                index++;
                            }
                            break;
                        }
                        default:
                        {
                            local.Clear();
                            foreach (T item in collection)
                                local.Add(new Variable<T>(item));
                            break;
                        }
                    }
                };

                onChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

                collection.CollectionChanged += onChanged;
                var subscription = local.Subscribe(o);
                return delegate
                {
                    collection.CollectionChanged -= onChanged;
                    subscription.Dispose();
                };
            });
        }
    }
}